/**
 * Una instancia dcli es un cliente del sistema,y deben
 * esperar comandos de usuarios por la consola,como 
 * realizar consultas u otras operaciones sobre los
 * programas drep correspondientes, en funcion de los
 * comandos que la instancia reciba.
 *
 * @author Colmenares Carlos (06-39380)
 * @author Tough Alex (07-41604)
 */
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

class DCLI
{   /**
     * Constructor de una instancia dcli del sistema.
     * 
     * @param ip_drep La direccion IPv4 de la maquina
     *                donde debe estar corriendo un
     *                programa drep, al que conectara 
     *                esta instancia dcli.
     * 
     * @param ldirectorio El camino dentro del sistema
     *                    de archivos local donde estaran
     *                    los archivos iniciales que podra
     *                    subir la instancia dcli, y donde
     *                    se almacenaran los archivos que 
     *                    la instancia dcli le solicita 
     *                    bajar al programa drep correspondiente.
     *
     * @param instrucciones Direccion de un archivo que contendra
     *                      en cada linea uno de los comandos que
     *                      puede usar una instancia dcli.
     *
     * @param nombre Nombre del usuario, que en ese momento este
     *               usando el usuario de la instancia dcli.
     * 
     * @param puerto Numero del puerto bien conocido, que un
     *               programa drep utilizara para esperar las
     *               peticiones de las instancias dcli.
     * 
    */
    public DCLI(InetAddress ip_drep,String ldirectorio,
		String instrucciones,String nombre,int puerto)
	    throws RuntimeException
    {	
	try{
	    //Inicializar conexion con servidor drep.
	    Socket cliente = new Socket(ip_drep,puerto); 
	    BufferedReader in = new BufferedReader(
			    new InputStreamReader(
			    cliente.getInputStream())); 
	    PrintWriter out = new PrintWriter(
			cliente.getOutputStream(), true);

	    BufferedReader std = new BufferedReader(new InputStreamReader(System.in)); 
	    String ins;
	    String line;  
	    out.println(Protocol.CONNECT);
	    out.println(nombre);
	    line = in.readLine();
	    if(!line.equals(Protocol.OK)){
		throw new RuntimeException();
	    }	    
	    
	    //Conexion con servidor drep inicializada.
	    else{
		//Procesar comandos en archivo.
		if(!instrucciones.equals("")){
		    ejecutarArchivo(instrucciones,ldirectorio,in,out);
		}

		System.out.print("dcli>");
		while(true){
		    //Procesar comandos de entrada estandar.
		    ins = std.readLine();
		    if(ins.equals("salir")){
			out.println(Protocol.SALIR);
			in.close();
			out.close();
			cliente.close();
			break;
		    }
		    try{
			dcli_prompt(in,out,ldirectorio,ins);
			System.out.print("dcli>");	    
		    }
		    catch(RuntimeException re1){
			System.out.println("Error inesperado"+
					    " de drep");
		    }
		}
		out.close();
		in.close();
	    }
	}
	catch(IOException ioe)
	{   System.out.println("Error de E/S.");}	
	catch(IllegalArgumentException iae)
	{   System.out.println("Argumento invalido.");}

    }  

    /**
      * Ejecuta los comandos que esta contenido en cada
      * uno de las lineas de un archivo.
      * 
      * @param archivo Nombre del archivo que contiene
      *                cada uno de los comandos.
      * 
      * @param ldirectorio Nombre del directorio local 
      *			   donde estan los archivos
      * 
      * @param in Canal de entrada para recibir mensajes
      *           del servidor.
      *
      * @param out Canal de salida para mandar mensajes
      *            hacia el servidor.
      * 
    */
    public static void ejecutarArchivo(String archivo,
			String ldirectorio,
			BufferedReader in, PrintWriter out) 
    {	
	try{
	    FileReader fr = new FileReader(archivo);
	    BufferedReader br = new BufferedReader(fr);
	    String ins;
	    while ((ins = br.readLine()) != null) {	
		if(ins.equals("salir")){
			out.println(Protocol.SALIR);
			break;
		}
		try{
		    dcli_prompt(in,out,ldirectorio,ins);	
		}
		catch(IllegalArgumentException iae){
		    System.out.println("Argumento invalido.");
		}
		catch(RuntimeException re2){
		    System.out.println("Error inesperado de drep.");
		}
		
	    }
	    br.close();
	    fr.close();
	}
	catch(IOException ie)
	{   System.out.println("Error de E/S.");}
    }

    /**
      * Ejecuta uno de los comandos que un usuario
      * puede pedir a la instancia dcli.
      *
      * @param in Canal de entrada para recibir mensajes
      *           del servidor.
      * 
      * @param out Canal de salida para mandar mensajes
      *            al servidor
      *
      * @param ldirectorio Nombre del directorio local 
      *			   donde estan los archivos
      *
      * @param instruccion Comando a pedir a la instancia dcli.
      * 
    */
    public static void dcli_prompt(BufferedReader in,
				PrintWriter out,
				String ldirectorio,
				String instruccion)
				throws RuntimeException,
				       IllegalArgumentException
    {	try{
	BufferedReader br = null;
        FileReader fr = null;
	String[] comandos = instruccion.split(" ");
	String comando = comandos[0];	 	
	String archivo_path = "";
	String archivo = "";
	String line = "";
	int nbytes = 0;

	if(comando.equals("ldir")){
	    Utilities.ldir(ldirectorio);
	}

	else if(comando.equals("cat")){
	    if(comandos.length != 2){
		System.out.println("uso: cat <nomb archivo>");
	    }
	    else{
	    archivo = comandos[1];
	    
	    if(!ldirectorio.endsWith("/")){
		archivo_path = ldirectorio + "/"+ archivo;	
	    }
	    else{
		archivo_path = ldirectorio + archivo;
	    }
	    
	    File file =  new File(archivo_path);
    	    if(!file.exists()){
		throw new IllegalArgumentException();
	    }	    
	    if(!file.canRead()){
		throw new IllegalArgumentException();
	    }
	    if(file.isDirectory()){
		throw new IllegalArgumentException();
	    }

	    if(Utilities.perteneceDirectorio(archivo,ldirectorio)){
		Utilities.cat(archivo_path);
	    }
	    else{
		System.out.println(archivo + " no se "+
		"encuentra en el directorio "+ldirectorio);
	    }
	    }
	}
		    	    
	else if(comando.equals("sube")){
	    if(comandos.length != 2){
		System.out.println("uso: sube <nomb archivo>");
	    }
	    else{
	    archivo = comandos[1];

    	    

	    if(!ldirectorio.endsWith("/")){
		archivo_path = ldirectorio + "/"+ archivo;	
	    }
	    else{
		archivo_path = ldirectorio + archivo;
	    }

	    File file = new File(archivo_path);
	    if(!file.exists()){
		throw new IllegalArgumentException();
	    }	    
	    if(!file.canRead()){
		throw new IllegalArgumentException();
	    }
	    if(file.isDirectory()){
		throw new IllegalArgumentException();
	    }
	    
	    fr = new FileReader(archivo_path);
	    br = new BufferedReader(fr);
	   
	    //Si el archivo es muy grande, abortar
            if( file.length() > Integer.MAX_VALUE/2 ){
		out.println(Protocol.ERROR_INESPERADO);
		throw new IOException("Archivo muy grande! "
                                +archivo);
            } 
	    char[] streamDeChars = new char[(int)file.length()];

	    
	    out.println(Protocol.SUBE);
	    out.println(archivo);
	    out.println(file.length());
	    br.read(streamDeChars, 0, (int)file.length());

            out.print(streamDeChars);
            out.flush();

	    line = in.readLine();
	    br.close();

	    if(line.equals(Protocol.ERROR_INESPERADO)){
		throw new RuntimeException();
	    }
	    }
	}
		    
	else if(comando.equals("baja")){
	    if(comandos.length != 2){
		System.out.println("uso: baja <nomb archivo>");
	    }
	    else{
	    String nombreArchivo = comandos[1];
 
	    out.println(Protocol.BAJA);
	    out.println(nombreArchivo);
	    line = in.readLine();
	    if(!line.equals(Protocol.OK)){
		throw new RuntimeException();
	    } 
	    else{
		line = in.readLine();
		nbytes = Integer.parseInt(line);

                BufferedWriter fout = 
                    new BufferedWriter( 
                            new FileWriter(ldirectorio+
                                "/"+nombreArchivo)
                            );
		
                if(nbytes > Integer.MAX_VALUE/2){
                    throw new IOException("Se intentó bajar un "+
                            "archivo muy pesado");
                }

                char[] archivoStream = new char[nbytes];

                in.read(archivoStream, 0, nbytes);


                fout.write(archivoStream,0,(int)nbytes);
                fout.close();
	    }
	}
	}
	
	else if(comando.equals("rdir")){
	    out.println(Protocol.RDIR);
	    line = in.readLine();
	    if(!line.equals(Protocol.OK)){
		throw new RuntimeException();
	    }
	    else{
		int j = 0;
		int m = Integer.parseInt(in.readLine());
		while(j<m){
		    System.out.println(in.readLine());
		    j++;
		}
	    }
	}
	
	else if(comando.equals("tuser")){
	    out.println(Protocol.TUSER);
	    line = in.readLine();
	    if(!line.equals(Protocol.OK)){
		throw new RuntimeException();
	    }
	    else{
		int j = 0;
		int m = Integer.parseInt(in.readLine());
		while(j<m){
		    System.out.println(in.readLine());
		    j++;
		}
	    }	
	}

	else if(comando.equals("nrep")){
	    out.println(Protocol.NREP);
	    line = in.readLine();
	    if(!line.equals(Protocol.OK)){
		throw new RuntimeException();
	    }
	    else{
		int j = 0;
		int m = Integer.parseInt(in.readLine());
		while(j<m){
		    System.out.println(in.readLine());
		    j++;
		}
	    }
	}
	else if(comando.equals("ayuda")){
	    System.out.println(
                "Ingrese el comando que desee que sea ejecutado, los "+
                "comandos reconocidos son los siguientes:"
            );
            System.out.println();
            System.out.println(
                "\tayuda:    Imprime este menú"        
            );
            System.out.println();
            System.out.println(
                "\tldir:     Se listarán los nombres y tamaños de cada "+
                "uno de los archivos pertenecientes al directorio " +
		"<ldirectorio> ."       
            );
            System.out.println();
	    System.out.println(
                "\trdir:     Se listarán los nombres y tamaños de cada "+
                "uno de los archivos pertenecientes al directorio "  +
		"<rdirectorio> del programa drep."       
            );
	    System.out.println();
	    System.out.println(
                "\ttuser:   Muestra una lista de todos los usuarios " + 
		"activos en el sistema."       
            );
	    System.out.println();
	    System.out.println(
                "\tnrep:     Indica el numero de programas drep que " +
		"estan activos en el sistema."       
            );
	    System.out.println();
	    System.out.println(
                "\tsube <nomb archivo>:     Sube el contenido de "+ 
		"<nomb archivo> al directorio <rdirectorio> de drep."
            );
	    System.out.println();
	    System.out.println(
                "\tbaja <nomb archivo>:     Baja el contenido de " + 
		"<nomb archivo> ubicado en drep hasta el directorio " +
		"local <ldirectorio> del programa dcli."
            );
	    System.out.println();
            System.out.println(
                "\tcat <nomb_archivo>: Lista por pantalla el contenido "+
                "del archivo local <nomb_archiv> que debe estar en "+
                "el directorio <ldirectorio>."
            );
            System.out.println();
            System.out.println(
                "\tsalir:    Terminar el programa."
            );
	}	
	else{
	    System.out.println( "Ingrese el comando 'ayuda'" + 
				" (sin comillas) "+
				"para información detallada");
	} 
    }
    catch(IOException ioe)
    {System.out.println("Error en E/S.");}
    }

    public static void main(String[] args)
    {	
	try{
	DCLI dcli = null;
	InetAddress ip_drep = InetAddress.getByName("localhost");
	File file = null;
	String ldirectorio = ".";
	String archivo = "";
	String nombre = "guest";
	int puerto = 2000;
	int i = 0;
	while(i<args.length)
	    {   if(args[i].equals("-h") && i+1 < args.length)
		{	ip_drep = InetAddress.getByName(args[++i]);}   
		else if(args[i].equals("-p") && i+1 < args.length)
		{	puerto = Integer.parseInt(args[++i]);}   
		else if(args[i].equals("-d") && i+1 < args.length)
		{	ldirectorio = args[++i];}   
		else if(args[i].equals("-a") && i+1 < args.length)
		{	archivo = args[++i];}   
		else if(args[i].equals("-n") && i+1 < args.length)
		{	nombre = args[++i];}   
		else{
		    imprimirUso();
		}
		i++; 
	    }
	    file = new File(ldirectorio);

	    if(!file.exists()){
		System.out.println(ldirectorio + 
				    " no es un directorio.");
		System.exit(1);
	    }
	    if(!file.isDirectory()){
		System.out.println(ldirectorio + 
				    " no es un directorio.");
		System.exit(1);
	    }

	    if(!file.canRead()){
		System.err.println("Error, no se puede leer" + 
				    " el directorio "+ 
				    file);
		System.exit(1);
	    }
	    if(!archivo.equals("")){
		file = new File(archivo);
		if(!file.exists()){
		    System.out.println("El archivo "+ archivo 
				    + "no existe.");
		    System.exit(1);
		}
		if(!file.canRead()){
		    System.err.println("Error, no se puede leer"+ 
					" el directorio "
					+file);
		    System.exit(1);
		} 
		
	    }
	    if(puerto > 65536){
		System.out.println("No existe este numero de puerto.");
		System.exit(1);
	    }
	    else{
		dcli = new DCLI(ip_drep,ldirectorio,archivo,nombre,puerto); 
	    }
	}
	catch(UnknownHostException unknownhost)
	{System.out.println("Direccion IP no tiene un formato valido.");}
    } 

    /**
     * Esta función imprime los flags que se deben proporcionar
     * al programa para su correto funcionamiento
     */
    private static void imprimirUso(){
        String mnsjError =
            "uso: dcli [-h <host de un drep>] "+ 
	    "[-p <puerto de un drep>] "+
	    "[-d <ldirectorio>] "+
	    "[-a <archivo>] "+
	    "[-n <nombre>]";
        System.out.println(mnsjError);
        System.exit(1);
    }

} 
